/* Piece randomizers for LOCKJAW, an implementation of the Soviet Mind Game

Copyright (C) 2006-2007 Damian Yerrick <tepples+lj@spamcop.net>

This work is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

Original game concept and design by Alexey Pajitnov.
The Software is not sponsored or endorsed by Alexey Pajitnov, Elorg,
or The Tetris Company LLC.

*/

package fizzpicks.tetrapulp.tetrapulp;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import static fizzpicks.tetrapulp.tetrapulp.Lockjaw.*;

/**
 * Created by will
 * Date: May 3, 2008 2:10:59 AM
 */
public class LJRandom {

// Order matters.  The history randomizers prefer to give out the
// front half early in the game.  You don't want to give an S or Z
// first, or you force a slide. You also don't want to give an O
// first, or you force a slide with OS or OZ.

    static final Set<LJP> piecesNoI = EnumSet.of(
            LJP.J, LJP.L, LJP.T, // front half: J L T
            LJP.O, LJP.S, LJP.Z  // back half: O S Z
    );

    static final Set<LJP> piecesTetrominoes = EnumSet.of(
            LJP.I, LJP.J, LJP.L, LJP.T, // front half: I J L T
            LJP.O, LJP.S, LJP.Z // back half: O S Z
    );


    static final Set<LJP> piecesWithSmall = EnumSet.of(
            LJP.I, LJP.J, LJP.L, LJP.T, LJP.L3,  // front half: I J L T L3
            LJP.O, LJP.I2, LJP.I3, LJP.S, LJP.Z  // back half: O I2 I3 S Z
            // make sure that the S and Z are in the back half
    );

    static final Set<LJP> piecesSZ = EnumSet.of(
            LJP.S, LJP.Z
    );

    static final Set<LJP> piecesI = EnumSet.of(
            LJP.I
    );

/*
    static final Set<Set<LJP>> pieceSets = new <Set<LJP>>();
    {
  piecesTetrominoes,
  piecesNoI,
  piecesSZ,
  piecesI,
  piecesWithSmall
};

static final int[] pieceSetSizes = {
  piecesTetrominoes.length,
  piecesNoI.length,
  piecesSZ.length,
  piecesI.length,
  piecesWithSmall.length
};
*/

    static void bagInitRandomize(LJField p) {
        p.permuPhase = 0;
    }

    private static ArrayList<LJP> getSet(LJRAND2 setNo) {
        return new ArrayList<LJP>(setNo.getSet());
    }

    static LJP bagRandomize(LJField p) {
        LJRAND2 setNo = p.pieceSet;
        final List<LJP> set = getSet(setNo);

        LJP piece;

        // If we're out of permutation pieces, make new ones by copying
        // from one of the rand#Bag arrays until we hit the -1 sentinel.
        if (p.permuPhase == 0) {

            int pos = 0;
            for (LJP ljp : set) {
                p.permuPiece[pos] = ljp;
                p.permuPiece[pos + set.size()] = ljp;
                pos++;
            }

            // Double bag: Add one randomly chosen piece to the bag
            if (p.randomizer == LJRAND._2BAG) {
                pos *= 2;
            }
            // 7+1 Bag (TOJ style): Add one randomly chosen piece to the bag
            else if (p.randomizer == LJRAND.BAGPLUS1) {
                p.permuPiece[pos++] = set.get(ljRandInt(p) % set.size());
            }
            p.permuPhase = pos;
        }

        // Choose a position in the remainder of the deck
        int r = (p.permuPhase > 1) ? ljRandInt(p) % p.permuPhase : 0;

        // Swap the top card with the card at this position
        piece = p.permuPiece[r];
        p.permuPiece[r] = p.permuPiece[--p.permuPhase];
        return piece;
    }

    static void mtbInitRandomize(LJField p) {
        LJRAND2 setNo = p.pieceSet;
        List<LJP> set = getSet(setNo);

        // At game start, front three are I, J, L
        // Back four (never dealt as first tetromino) are O, S, T, Z
        System.arraycopy((LJP[]) set.toArray(), 0, p.permuPiece, 0, set.size());
    }

    static LJP mtbRandomize(LJField p) {
        LJRAND2 setNo = p.pieceSet;
        int len = setNo.getSet().size();

        // Choose a piece from the three in front
        int r = ljRandInt(p) % (len / 2);
        LJP piece = p.permuPiece[r];

        // Move it to the back
        for (; r < len - 1; ++r) {
            p.permuPiece[r] = p.permuPiece[r + 1];
        }
        p.permuPiece[len - 1] = piece;
        return piece;
    }

    static void tgmInitRandomize(LJField p) {
        LJRAND2 setNo = p.pieceSet;
        int len = setNo.getSet().size();

        p.permuPiece[0] = LJP.I;
        for (int i = len / 2; i < len; ++i) {
            p.permuPiece[i] = (i & 1) != 0 ? LJP.Z : LJP.S;
        }
    }

    /* State of TGM randomizer:
    * permuPiece[3..6]: history, [3] most recent
    * permuPiece[2]: 0 for first piece (use equal probability I, J, L, T)
    *   or 1 for subsequent pieces
    */
    static LJP tgmRandomize(LJField p) {
        LJRAND2 setNo = p.pieceSet;
        List<LJP> set = getSet(setNo);
        int len = set.size();

        int r = (ljRandInt(p) ^ (ljRandInt(p) << 15));
        LJP piece = LJP.NONE;

        if (p.permuPiece[0] == LJP.NONE) {

            // Roll up to 6 times for pieces
            for (int rolls = 6;
                 rolls > 0;
                 --rolls, r /= len) {
                piece = set.get(r % len);
                boolean found = false;

                // If the piece is not in the history, use it now
                for (int histoPos = len / 2;
                     !found && histoPos < len;
                     ++histoPos) {
                    if (piece == p.permuPiece[histoPos]) {
                        found = true;
                    }
                }
                if (!found) {
                    break;
                }
            }
        } else {
            p.permuPiece[0] = LJP.I;
            // Generate only pieces in the first half of the list
            piece = set.get(r % ((len + 1) / 2));
            if (piece == LJP.O) {
                piece = LJP.T;
            }
        }

        // Move it to the back
        for (r = len / 2; r < len - 1; ++r) {
            p.permuPiece[r] = p.permuPiece[r + 1];
        }
        p.permuPiece[len - 1] = piece;
        return piece;
    }

    /**
     * Sets up the randomizer.
     */
    static void initRandomize(LJField p) {
        LJRAND2 setNo = p.pieceSet;
        int len = setNo.getSet().size();

        if (len < 2) {
            p.randomizer = LJRAND.PURE;
        }

        switch (p.randomizer) {
            case PURE:
                break;
            case BAG:
            case BAGPLUS1:
            case _2BAG:
                bagInitRandomize(p);
                break;
            case HIST_INF:
                mtbInitRandomize(p);
                break;
            case HIST_6:
                tgmInitRandomize(p);
                break;
        }
    }

    /**
     * Chooses a pseudo-random piece.
     *
     * @param p the field on which the piece will be generated
     * @return the number of this piece
     *         (0=i, 1=j, 2=l, 3=o, 4=s, 5=t, 6=z)
     */
    static LJP randomize(LJField p) {
        switch (p.randomizer) {

            case BAG:
            case BAGPLUS1:
            case _2BAG:
                return bagRandomize(p);
            case HIST_INF:
                return mtbRandomize(p);
            case HIST_6:
                return tgmRandomize(p);
            case PURE:
            default: {
                LJRAND2 setNo = p.pieceSet;
                List<LJP> set = getSet(setNo);
                int len = set.size();
                return set.get(ljRandInt(p) % len);
    }
  }
}

}
